#region Copyright (c) 2003, newtelligence AG. All rights reserved.
/*
// Copyright (c) 2003, newtelligence AG. (http://www.newtelligence.com)
// Original BlogX Source Code: Copyright (c) 2003, Chris Anderson (http://simplegeek.com)
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are permitted
// provided that the following conditions are met:
//
// (1) Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
// (2) Redistributions in binary form must reproduce the above copyright notice, this list of
// conditions and the following disclaimer in the documentation and/or other materials
// provided with the distribution.
// (3) Neither the name of the newtelligence AG nor the names of its contributors may be used
// to endorse or promote products derived from this software without specific prior
// written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
// OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
// IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
// OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// -------------------------------------------------------------------------
//
// Original BlogX source code (c) 2003 by Chris Anderson (http://simplegeek.com)
//
// newtelligence is a registered trademark of newtelligence Aktiengesellschaft.
//
// For portions of this software, the some additional copyright notices may apply
// which can either be found in the license.txt file included in the source distribution
// or following this notice.
//
*/
#endregion

using System;
using System.Collections;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Xml;
using System.Xml.Serialization;

namespace newtelligence.DasBlog.Runtime
{
	[Serializable]
	[XmlRoot(Namespace=Data.NamespaceURI)]
	[XmlType(Namespace=Data.NamespaceURI)]
	public class Entry : EntryBase, IComparable
	{
		string _title;
		string _link; // A hyperlink that the entry is discussing or referring.
		string _description;
		string _categories;
		string _author;
		bool _isPublic = true;
		bool _showOnFrontPage = true;
		bool _allowComments= true;
		CrosspostCollection _crossposts = new CrosspostCollection();
		AttachmentCollection _attachments = new AttachmentCollection();

		public string Description { get { return _description; } set { _description = value; } }
		public string Title { get { return _title; } set { _title = value; } }

		internal static string InternalCompressTitle(string title)
		{
			string retVal = String.Empty;
			if (title != null)
			{
				bool upper = false;

				foreach(char c in title)
				{
					if (char.IsWhiteSpace(c)) 
					{
						retVal += "+";
						upper = true;
					}

					if (char.IsLetterOrDigit(c))
					{
						if (upper) 
						{
							retVal += char.ToUpper(c);
							upper = false;
						}
						else
						{
							retVal += c;
						}
					}
				}
			}
			return retVal;
		}


		[XmlIgnore]
		public string CompressedTitle { get { return Entry.InternalCompressTitle(_title); } }
		public string Link { get { return _link; } set { _link = value; } }
		public string Categories { get { return _categories; } set { _categories = value; } }
		public string Author { get { return _author; } set { _author = value; } }
		public bool IsPublic { get { return _isPublic; } set { _isPublic = value; } }
		public bool ShowOnFrontPage { get { return _showOnFrontPage; } set { _showOnFrontPage = value; } }
		public bool AllowComments { get { return _allowComments; } set { _allowComments = value; } }
		
		[XmlIgnore]
		public AttachmentCollection Attachments { get { return _attachments; } }
		[XmlArray("Attachments")]
		public Attachment[] AttachmentsArray
		{
			get
			{
                return new ArrayList(_attachments).ToArray(typeof(Attachment)) as Attachment[];
			}
			set
			{
				if ( value == null )
				{
					_attachments = new AttachmentCollection();
				}
				else
				{
					_attachments = new AttachmentCollection(value);
				}
			}
		}

        [XmlIgnore]
        public Attachment Enclosure
        {
            get
            {
                foreach(Attachment attachment in this._attachments)
                {
                    if (attachment.AttachmentType == AttachmentType.Enclosure)
                        return attachment;
                }

                return null;
            }
        }

		[XmlIgnore]
		public CrosspostCollection Crossposts { get { return _crossposts; } }
		[XmlArray("Crossposts")]
		public Crosspost[] CrosspostArray
		{
			get
			{
				return new ArrayList(_crossposts).ToArray(typeof(Crosspost)) as Crosspost[];
			}
			set
			{
				if ( value == null )
				{
					_crossposts = new CrosspostCollection();
				}
				else
				{
					_crossposts = new CrosspostCollection(value);
				}
			}
		}

		[XmlAnyElement]
		public XmlElement[] anyElements;
		[XmlAnyAttribute]
		public XmlAttribute[] anyAttributes;

		public string[] GetSplitCategories()
		{
			if (Categories == null || Categories.Length == 0)
			{
				return new string[0];
			}
			else
			{
				return Categories.Split(';');
			}
		}

		/// <summary>
		/// Returns a copy of the current entry
		/// </summary>
		/// <returns></returns>
		public Entry Clone()
		{
			Entry clone = null; //Not sure why the previous code created a new blank object just to overwrite it
			XmlSerializer ser = new XmlSerializer(typeof(Entry),Data.NamespaceURI);
			using (MemoryStream ms = new MemoryStream())
			{
				ser.Serialize(ms, this);
				ms.Position = 0;
				clone = ser.Deserialize(ms) as Entry;
			}
			return clone;
		}

		/// <summary>
		/// Delegate callback used to filter items that are not public.
		/// </summary>
		/// <param name="entry"></param>
		/// <returns></returns>
		public static bool IsPublicEntry(Entry entry)
		{
			return entry.IsPublic;
		}

		/// <summary>
		/// Delegate callback used to filter itmes that are not front page items.
		/// </summary>
		/// <param name="entry"></param>
		/// <returns></returns>
		public static bool ShowOnFrontPageEntry(Entry entry)
		{
			return entry.ShowOnFrontPage;
		}

		/// <summary>
		/// Returns true if the entry is in the categoryName specified.
		/// </summary>
		/// <param name="entry">The entry to check for category</param>
		/// <param name="categoryName">
		/// The name of the category to check.  If categoryName is null or empty check
		/// whether the entry has no categories,
		/// </param>
		/// <returns>
		/// A value of true indicates the entry is in the category specified or, if the categoryName
		/// is null or empty, return true if the entry is assigned no categories
		/// </returns>
		public static bool IsInCategory(Entry entry, string categoryName)
		{
			bool categoryFound = false;
			if(categoryName == null && categoryName.Length == 0)
			{
				categoryFound = (entry.Categories == null && entry.Categories.Length == 0);
			}
			else
			{
				foreach ( string entryCategory in entry.GetSplitCategories() )
				{
					if ( entryCategory.ToUpper() == categoryName.ToUpper() )
					{
						categoryFound = true;
						break;
					}
				}
			}
			return categoryFound;
		}

		/// <summary>
		/// Returns true if the entry is in the categoryName specified.
		/// </summary>
		/// <param name="entry">The entry to check for category</param>
		/// <param name="dateTime">
		/// The latest date and time the entry can occur.
		/// </param>
		/// <returns>
		/// A value of true indicates the entry is in the category specified or, if the categoryName
		/// is null or empty, return true if the entry is assigned no categories
		/// </returns>
		public static bool OccursBefore(Entry entry, TimeZone timeZone, DateTime dateTime)
		{
			return (timeZone.ToLocalTime(entry.CreatedUtc) <= dateTime);
		}
	
		public static bool OccursBetween(Entry entry, TimeZone timeZone, 
			DateTime startDateTime, DateTime endDateTime)
		{
			return ((timeZone.ToLocalTime(entry.CreatedUtc) >= startDateTime)
				&& (timeZone.ToLocalTime(entry.CreatedUtc) <= endDateTime) );
		}

		/// <summary>
		/// Returns true if the specified Entry is within the same month as <c>month</c>;
		/// </summary>
		/// <param name="entry"></param>
		/// <param name="timeZone"></param>
		/// <param name="month"></param>
		/// <returns></returns>
		public static bool OccursInMonth(Entry entry, TimeZone timeZone, 
			DateTime month)
		{
			DateTime startOfMonth = new DateTime(month.Year, month.Month, 1, 0, 0, 0);
			DateTime endOfMonth = new DateTime(month.Year, month.Month, 1, 0, 0, 0);
			endOfMonth = endOfMonth.AddMonths(1);
			endOfMonth = endOfMonth.AddSeconds(-1);
			//TimeSpan offset = timeZone.GetUtcOffset(endOfMonth);
			//endOfMonth = endOfMonth.AddHours(offset.Negate().Hours);
			return ( OccursBetween(entry, timeZone, startOfMonth, endOfMonth) );
		}

		/// <summary>
		/// Returns true if the specified entry has is an accepted language.
		/// </summary>
		/// <param name="entry">The entry whose Language property is checked.</param>
		/// <param name="acceptLanguages">A comma separated list of accepted languages.</param>
		/// <returns>
		/// Returns true if the entry's language is in the acceptedLanguages list or 
		/// else acceptedLanguages is null/empty and the entry's language is null/empty.
		/// </returns>
		public static bool IsInAcceptedLanguages(Entry entry, string acceptLanguages)
		{
			bool languageFound = false;
			string language;

			// If acceptLanguages is empty or null then check to see
			// whether the entry has no languages.
			if(acceptLanguages == null && acceptLanguages.Length == 0)
			{
				languageFound = (entry.Language == null && entry.Language.Length == 0);
			}
			else
			{
				foreach ( string eachLanguageElement in acceptLanguages.Split(',') )
				{
					language = eachLanguageElement.Split(';')[0];
					if ( language.ToUpper().StartsWith(entry.Language.ToUpper()))
					{
						languageFound = true;
						break;
					}
				}
			}
			return languageFound;
		}
		#region IComparable Members

		/// <summary>
		/// Returns true of all the simple properties are equal.
		/// </summary>
		/// <param name="entry">The <see cref="Entry"/>to compare to</param>
		/// <returns>0 if the entries contain equal simple properties</returns>
		/// <remarks>
		///		CompareTo will do a comparison on the following properties.
		///		<list type="bullet">
		///		<term><see cref="Entry.Title"/></term>
		///		<description>description</description>
		///		<item>
		///		<term><see cref="Entry.Description"/></term>
		///		</item>
		///		<item>
		///		<term><see cref="Entry.Content"/></term>
		///		</item>
		///		<item>
		///		<term><see cref="Entry.Categories"/></term>
		///		</item>
		///		<item>
		///		<term><see cref="Entry.Author"/></term>
		///		</item>
		///		<item>
		///		<term><see cref="Entry.Link"/></term>
		///		</item>
		///		<item>
		///		<term><see cref="Entry.Attachments"/></term>
		///		</item>
		///		</list>
		/// </remarks>
		public int CompareTo(object entry)
		{
			bool different = false;
			if (entry is Entry)
			{
				Entry entry2 = entry as Entry;
				// we will only change the mod date if there has been a change to a few things
				if (this.Title != entry2.Title ||
					this.Description != entry2.Description ||
					this.Content != entry2.Content ||
					this.Categories != entry2.Categories ||
					this.Author != entry2.Author ||
					this.Link != entry2.Link ||
					this.Attachments.Count != entry2.Attachments.Count)
				{

					different = true;
				}
				else if (this.Attachments.Count == entry2.Attachments.Count)
				{
					// we need to also check the Attachments in calse they are modified
					for (int i = 0; i < this.Attachments.Count; i++)
					{
						if (this.Attachments[i].Url != entry2.Attachments[i].Url)
						{
							different = true;
						}
					}
				}

				return Convert.ToInt32(different);
			}
			else
			{
				throw new ArgumentException("entry is not an Entry");
			}
		}

		#endregion
	}
}
